/* Name: main.c
 * Based on project: hid-data, example how to use HID for data transfer
 * (Uses modified descriptor and usbFunctionSetup from it.)
 * Original author: Christian Starkjohann
 * Arduino modifications by: Philip J. Lindsay
 * Creation Date: 2008-04-11
 * Tabsize: 4
 * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
 * This Revision: $Id: main.c 692 2008-11-07 15:07:40Z cs $
 */

/*
This example should run on most AVRs with only little changes. No special
hardware resources except INT0 are used. You may have to change usbconfig.h for
different I/O pins for USB. Please note that USB D+ must be the INT0 pin, or
at least be connected to INT0 as well.
*/

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>  /* for sei() */
#include <util/delay.h>     /* for _delay_ms() */
#include <avr/eeprom.h>
#include <avr/pgmspace.h>   /* required by usbdrv.h */

#include "UsbPacket.h"

// Ring buffer implementation nicked from HardwareSerial.cpp
// TODO: Don't nick it. :)
ring_buffer rx_buffer = { { { { 0 } } }, 0, 0 };
ring_buffer tx_buffer = { { { { 0 } } }, 0, 0 };


inline int store_packet(struct usbpacket *p, ring_buffer *the_buffer)
{
  int i = (the_buffer->head + 1) % RING_BUFFER_SIZE;

  // if we should be storing the received character into the location
  // just before the tail (meaning that the head would advance to the
  // current location of the tail), we're about to overflow the buffer
  // and so we don't write the character or advance the head.
  if (i != the_buffer->tail) {
    int j;
    for(j=0;j<PACKET_SIZE;j++)
      the_buffer->buffer[the_buffer->head].byte[j] = p->byte[j];
    the_buffer->head = i;
    return 0;
  } else {
    return -1;
  }
}
 
inline int retrieve_packet(struct usbpacket *p, ring_buffer *the_buffer)
{
  // if the head isn't ahead of the tail, we don't have any characters
  if (the_buffer->head == the_buffer->tail) {
    return 0;
  } else {
    int j;
    for(j=0;j<PACKET_SIZE;j++)
      p->byte[j] = the_buffer->buffer[the_buffer->tail].byte[j];
    the_buffer->tail = (the_buffer->tail + 1) % RING_BUFFER_SIZE;
    return PACKET_SIZE;
  }
}

inline int available_packet(ring_buffer *the_buffer)
{
  return (RING_BUFFER_SIZE + the_buffer->head - the_buffer->tail) % RING_BUFFER_SIZE;
}

 
UsbPacketDevice::UsbPacketDevice(ring_buffer *rx_buffer,
				 ring_buffer *tx_buffer) {
  _rx_buffer = rx_buffer;
  _tx_buffer = tx_buffer;
}

void UsbPacketDevice::begin(char *ProductNameString) {
    USBSetProductName(ProductNameString);

    // disable timer 0 overflow interrupt (used for millis)
    TIMSK0&=!(1<<TOIE0);

    cli();

    usbInit();
      
    usbDeviceDisconnect();
    uchar   i;
    i = 0;
    while(--i){             /* fake USB disconnect for > 250 ms */
        _delay_ms(1);
    }
    usbDeviceConnect();

    sei();

    memset(reportBuffer, 0, sizeof(reportBuffer));  
    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
  }
    
// TODO: Deprecate update
void UsbPacketDevice::update() {
  refresh();
}

void UsbPacketDevice::refresh() {
  usbPoll();
  if(available_packet(_tx_buffer) && usbInterruptIsReady()) {
    if(retrieve_packet((struct usbpacket*)reportBuffer, _tx_buffer)) {
      usbSetInterrupt(reportBuffer, PACKET_SIZE);
    }
  }
}

int UsbPacketDevice::available() {
  /*
   */
  return available_packet(_rx_buffer);
}
  
int UsbPacketDevice::read(struct usbpacket *p) {
  /*
   */
  return retrieve_packet(p, _rx_buffer);
}

int UsbPacketDevice::write(struct usbpacket *p) {
  /*
   */
  return store_packet(p, _tx_buffer);
}






/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

#ifdef __cplusplus
extern "C"{
#endif 
PROGMEM char usbHidReportDescriptor[17] = { /* USB report descriptor */
  0x05, 0x01,                    // USAGE_PAGE (Generic Desktop) 
  0x09, 0x01,                    // USAGE (Vendor Usage 1)  
  0xa1, 0x01,                    // COLLECTION (Application) 
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0) 
  0x25, 0xff,                    //   LOGICAL_MAXIMUM (1) 
  0x75, 0x08,                    //   REPORT_SIZE (8) 
  0x95, PACKET_SIZE,             //   REPORT_COUNT
  0x81, 0x02,                    //   INPUT (Data,Var,Abs)
  0xc0                           // END_COLLECTION 
};


/* ------------------------------------------------------------------------- */

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
  usbRequest_t    *rq = (usbRequest_t*)((void *)data);

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* HID class request */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* since we have only one report type, we can ignore the report-ID */
	    static uchar dataBuffer[PACKET_SIZE];  /* buffer must stay valid when usbFunctionSetup returns */
            int len = retrieve_packet((struct usbpacket*)dataBuffer, &tx_buffer);
	    if (len > 0 ) {
	      usbMsgPtr = dataBuffer; /* tell the driver which data to return */
	      return len; /* tell the driver to send the available bytes */
	    } else {
	      // Drop through to return 0 (which will stall the request?)
	    }
        }else if(rq->bRequest == USBRQ_HID_SET_REPORT){
            /* since we have only one report type, we can ignore the report-ID */

	  // TODO: Check race issues?
	  //store_char(rq->wIndex.bytes, &rx_buffer);
          return USB_NO_MSG;
        }
    }else{
        /* ignore vendor type requests, we don't use any */
    }
    return 0;
}

uchar usbFunctionWrite(uchar *data, uchar len)
{
    if ( len != PACKET_SIZE )
        return 0xff; //STALL
    store_packet((struct usbpacket*)data, &rx_buffer);
    return 1; // OK
}


// Adapted from Dynamic Serial Number at runtime from http://forums.obdev.at/viewtopic.php?f=8&t=2284
#define PRODUCT_NAME_LENGTH 20 // the number of characters required for your serial number

static int productNameDescriptor[PRODUCT_NAME_LENGTH + 1];

uchar usbFunctionDescriptor(usbRequest_t *rq)
{
   uchar len = 0;
   usbMsgPtr = 0;
   if (rq->wValue.bytes[1] == USBDESCR_STRING && rq->wValue.bytes[0] == 2) // 2 is the type of string descriptor, in this case the device serial number
   {
      usbMsgPtr = (uchar*)productNameDescriptor;
      len = sizeof(productNameDescriptor);
   }
   return len;
}

void USBSetProductName(char *string)
{
   productNameDescriptor[0] = USB_STRING_DESCRIPTOR_HEADER(PRODUCT_NAME_LENGTH);
   uchar i;
   for(i=0;i<PRODUCT_NAME_LENGTH;i++)
     productNameDescriptor[i+1] = i < strlen(string) ? string[i] : ' ';
}

#ifdef __cplusplus
} // extern "C"
#endif

UsbPacketDevice UsbPacket = UsbPacketDevice(&rx_buffer, &tx_buffer);
